#region using directives

using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Net.Sockets;

#endregion

// minimalistic telnet implementation
// conceived by Tom Janssens on 2007/06/06  for codeproject
//
// http://www.corebvba.be

namespace FRS_LIB.Telnet
{
    /// <summary>Class that can be used for a telnet connection.</summary>
    public class TelnetClient
    {
        #region enumerations
        private enum Options
        {
            Sga = 3
        }

        private enum Verbs
        {
            Will = 251,
            Wont = 252,
            Do = 253,
            Dont = 254,
            Iac = 255
        }
        #endregion

        #region private members
        private readonly TcpClient tcpSocket;
        private string command;
        private bool disableEcho;
        #endregion

        #region properties
        /// <summary>Gets whether the client is connected to a host.</summary>
        public bool IsConnected { get { return tcpSocket.Connected; } }
        #endregion

        #region constructors

        /// <summary>Constructor (using the telnet port 23).</summary>
        /// <param name="hostname">The hostname (e.g. IP address).</param>
        public TelnetClient(string hostname) : this(hostname, 23) { }

        /// <summary>Constructor.</summary>
        /// <param name="hostname">The hostname (e.g. IP address).</param>
        /// <param name="port">The port (normally 23 for telnet).</param>
        public TelnetClient(string hostname, int port)
        {
            tcpSocket = new TcpClientWithTimeout(hostname, port, 500).Connect();
            command = String.Empty;
        }

        #endregion

        #region public functions
        /// <summary>Disables the echo.</summary>
        public void DisableEcho() { disableEcho = true; }

        /// <summary>Login to the currently connected host.</summary>
        /// <param name="username">The username.</param>
        /// <param name="password">The password.</param>
        /// <returns>The respond string.</returns>
        public string Login(string username,string password)
        {
            string s = Read();

            WriteLine(String.Format("login {0} {1}", username, password));

            s = Read();
            s = s.TrimEnd();

            if (!s.ToUpper(CultureInfo.CurrentCulture).Contains("250 OK")) throw new TelnetClientException(TelnetClientException.Errors.LoginNotPossible, "Failed to connect (wrong user or password).");
            
            return s;
        }

        /// <summary>Closes the connection.</summary>
        public void Close()
        {
            tcpSocket.Close();
        }

        /// <summary>Sends a command with new line to the host.</summary>
        /// <param name="cmd">The command.</param>
        public void WriteLine(string cmd) { Write(cmd + "\r\n"); }

        /// <summary>Sneds a command to the host.</summary>
        /// <param name="cmd">The command.</param>
        public void Write(string cmd)
        {
            command = cmd;
            if (!tcpSocket.Connected) return;
            byte[] buf = Encoding.ASCII.GetBytes(cmd.Replace("\0xFF","\0xFF\0xFF"));
            tcpSocket.GetStream().Write(buf, 0, buf.Length);
        }

        #endregion

        #region private functions

        public string Read()
        {
            if (!tcpSocket.Connected) return null;
            StringBuilder sb = new StringBuilder();
            const int sleep = 10;
            int currentTimeout = 100;
            bool stop = false;
            do
            {
                ParseTelnet(sb);
                System.Threading.Thread.Sleep(sleep);
                currentTimeout -= sleep;
                if (currentTimeout == 0) stop = true;
                if (sb.Length > 0) stop = true;
            } while (!stop);

            string ret = sb.ToString();
            if (disableEcho && command != String.Empty && ret.StartsWith(command))
                ret = ret.Substring(command.Length).TrimStart(' ');

            command = String.Empty;
            return ret;
        }

        private void ParseTelnet(StringBuilder sb)
        {
            while (tcpSocket.Available > 0)
            {
                int input = tcpSocket.GetStream().ReadByte();
                switch (input)
                {
                    case -1 :
                        break;
                    case (int)Verbs.Iac:
                        // interpret as command
                        int inputverb = tcpSocket.GetStream().ReadByte();
                        if (inputverb == -1) break;
                        switch (inputverb)
                        {
                            case (int)Verbs.Iac: 
                                //literal Iac = 255 escaped, so append char 255 to string
                                sb.Append(inputverb);
                                break;
                            case (int)Verbs.Do: 
                            case (int)Verbs.Dont:
                            case (int)Verbs.Will:
                            case (int)Verbs.Wont:
                                // reply to all commands with "Wont", unless it is Sga (suppres go ahead)
                                int inputoption = tcpSocket.GetStream().ReadByte();
                                if (inputoption == -1) break;
                                tcpSocket.GetStream().WriteByte((byte)Verbs.Iac);
                                if (inputoption == (int)Options.Sga )
                                    tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.Do ? (byte)Verbs.Will:(byte)Verbs.Do); 
                                else
                                    tcpSocket.GetStream().WriteByte(inputverb == (int)Verbs.Do ? (byte)Verbs.Wont : (byte)Verbs.Dont); 
                                tcpSocket.GetStream().WriteByte((byte)inputoption);
                                break;
                        }
                        break;
                    default:
                        sb.Append( (char)input );
                        break;
                }
            }
        }
        #endregion
    }
}
